﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using snat.algorithms.network.TFIDF;
using snat.model;
using System.IO;
using System.Threading.Tasks;

namespace snat.algorithms.node.TFIDF {
    public class ForwardTFIDFNode1<T> : TFIDFNode<T> where T : Document {

        private double threshold;
        public ForwardTFIDFNode1(int _id, string _name, double threshold, int initialCluster = 0)
            : base(_id, _name, initialCluster) {
            this.threshold = threshold;
        }
        public ForwardTFIDFNode1(int _id, string _name, int initialCluster = 0)
            : this(_id, _name, 0.75, initialCluster) { }

        public Dictionary<Document, double> ForwardedDocuments = new Dictionary<Document, double>();
        public Dictionary<Document, double> Predictions = new Dictionary<Document, double>();

        public static int forwardCount = 0;
        public static int correct = 0;
        public static int wrong = 0;
        private static double sumValue = 0;
        private static int totalPredictions = 0;
        public static double AvgValue {
            get {
                return sumValue / (double)totalPredictions;
            }
        }


        public override void OnMessageSend(Message m, BaseNode n) {
            Console.WriteLine(NetworkModel.Instance.MessageProcessedCount);
            bool updateTerms;
            lock (MessagesAddedToTerms) {
                if (!MessagesAddedToTerms.Contains(m.ID)) {
                    //first node to process
                    updateTerms = true;
                    MessagesAddedToTerms.Add(m.ID);
                    //Console.WriteLine("{0}, FIRST!!!!!!LOLROFL!?!!??##!", m.ID);
                } else {
                    //Console.WriteLine("{0}, second :(", m.ID);
                    //message already processed by other node, don't add its words to Terms
                    updateTerms = false;
                }
            }
            //create the document using dirty reflection
            T document = (T)Activator.CreateInstance(
                            typeof(T),
                            new object[] { m, Terms, updateTerms });
            SentDocuments.Add(document);

            Parallel.ForEach<Term>(Terms.Values,
                    term => { term.IDF = Math.Log((double)DocumentCount / (double)term.AppearenceCount); });
            Dictionary<Term, double> termsDict = Terms.Values.ToDictionary(e => e, e => e.IDF);
            Parallel.ForEach<Document>(ReceivedDocuments,
                d => { d.PopulateTFIDF(termsDict); });
            document.PopulateTFIDF(termsDict);

            //for all documents, see if we've 'forwarded' one
            if (!ForwardedDocuments.Any(e => e.Key.CosineSimilarity(document) > threshold)) {
                foreach (Document d in ReceivedDocuments) {
                    double cs = document.CosineSimilarity(d);
                    if (cs > threshold) {
                        if (!ForwardedDocuments.ContainsKey(document)) {
                            ForwardedDocuments.Add(document, cs);
                        } else {
                            ForwardedDocuments[document] = Math.Max(ForwardedDocuments[document], cs);
                        }
                        Console.WriteLine("FORWARDED!, Probability : {0}, ID : {1}", ForwardedDocuments[document].ToString("F3"), document.ID);
                        //take note of it for if anotehr message is received that is similar!!!
                        forwardCount++;
                    }
                }
            }


            foreach (Document d in Predictions.Keys.Where(e => document.CosineSimilarity(e) > threshold).ToList()) {
                Console.WriteLine("The prediction for {0} was correct!!", d.ID);
                Predictions.Remove(d);
                correct++;
            }
        }

        public override void OnMessageReceive(Message m, BaseNode n) {
            bool updateTerms;
            lock (MessagesAddedToTerms) {
                if (!MessagesAddedToTerms.Contains(m.ID)) {
                    //first node to process
                    updateTerms = true;
                    MessagesAddedToTerms.Add(m.ID);
                    // Console.WriteLine("{0}, FIRST!!!!!!LOLROFL!?!!??##!", m.ID);
                } else {
                    //Console.WriteLine("{0}, second :(", m.ID);
                    //message already processed by other node, don't add its words to Terms
                    updateTerms = false;
                }
            }
            //create the document using dirty reflection
            T document = (T)Activator.CreateInstance(
                            typeof(T),
                            new object[] { m, Terms, updateTerms });

            Parallel.ForEach<Term>(Terms.Values,
                    term => { term.IDF = Math.Log((double)DocumentCount / (double)term.AppearenceCount); });
            Dictionary<Term, double> termsDict = Terms.Values.ToDictionary(e => e, e => e.IDF);
            Parallel.ForEach<Document>(ReceivedDocuments,
                d => { d.PopulateTFIDF(termsDict); });
            document.PopulateTFIDF(termsDict);

            /*Predict if there is a forward or not, and how likely that will be*/
            int forwards = 0;
            int receives = 0;
            foreach (Document d in ForwardedDocuments.Keys.Where(e => document.CosineSimilarity(e) > threshold)) {
                forwards++;
            }
            foreach (Document d in ReceivedDocuments.Where(e => document.CosineSimilarity(e) > threshold)) {
                receives++;
            }
            bool executed = false;
            //update old values if there
            foreach (Document d in Predictions.Keys.Where(e => document.CosineSimilarity(e) > threshold).ToList()) {
                Predictions[d] = Math.Max((double)forwards / (double)receives, Predictions[d]);
                executed = true;
            }
            //if not, add one for this document
            if (!executed && forwards > 0 && receives > 0) {
                Predictions.Add(document, Math.Min(1, (double)forwards / (double)receives));
                sumValue += Predictions[document];
                totalPredictions++;
                Console.WriteLine("PREDICTION!, Probability : {0}, ID : {1}", Predictions[document].ToString("F3"), document.ID);
            }
            
            ReceivedDocuments.Add(document);
        }

        private static DateTime start = NetworkModel.Instance.CurrentTime;
        private static TimeSpan oneWeek = new TimeSpan(3, 0, 0, 0, 0);
        private static TimeSpan fourWeeks = new TimeSpan(28, 0, 0, 0, 0);
        public override void NetworkMessageEveryone(Message m) {
            ReceivedDocuments.RemoveAll(e => e.Date < NetworkModel.Instance.CurrentTime - oneWeek);
            SentDocuments.RemoveAll(e => e.Date < NetworkModel.Instance.CurrentTime - oneWeek);
            List<Document> toRemove = Predictions.Keys.Where(e => e.Date < NetworkModel.Instance.CurrentTime - oneWeek).ToList();
            foreach (Document d in toRemove) {
                wrong++;
                Predictions.Remove(d);
            }
            //toRemove = ForwardedDocuments.Keys.Where(e => e.Date < NetworkModel.Instance.CurrentTime - oneWeek).ToList();
            //foreach (Document d in toRemove) {
            //    ForwardedDocuments.Remove(d);
            //}
        }

        private static StreamWriter output = new StreamWriter("../../../forwardPredict.txt");
        public override void NetworkMessageOnce(Message m) {
            
        }

        public static void End() {
            Console.WriteLine("Predictions wrong: {0}, Predictions correct: {1}, Average prediction: {2}",
                wrong, correct, AvgValue);
            output.Close();
        }
        private static bool registered = false;
        public override void Register() {
            base.Register();
            if (!registered) {
                registered = true;
                NetworkModel.Instance.TapeEnded += End;
                NetworkModel.Instance.TapePaused += End;
            }
        }

        public override void DeRegister() {
            base.DeRegister();
            if (registered) {
                registered = false;
                NetworkModel.Instance.TapeEnded -= End;
                NetworkModel.Instance.TapePaused -= End;
            }
        }

    }
}
